Hola, bienvenid@ a mi sitio personal.
Acá encontrarás algunos textos, proyectos y enlaces en los que he trabajado.
¡Si los encuentras interesante, no dudes en opinar o contactarme!
Proyectos destacados
Actualmente estoy disfrutando desarrollar con la librería streamlit. Algunos proyectos recientes:
- Convertir pptx a RISE: Presentado en la Pycon Latam 2021.
- Gráficos en xkcd: ¡Destacado por Streamlit en su Weekly Roundup!
- Code snippets de streamlit: Un work-in-progress.
En proyectos encontrarás todos los proyectos.
Original
PyCon Latam 2021
Mi tercera PyCon
Tutorial de RISE
Usando jupyter notebooks para presentaciones interactivas
Experiencias del Sprint de Data Umbrella
Lo que aprendí en mi primer sprint contribuyendo a código abierto.
El detective: entre la lógica y el azar
Las raíces de la serendipia y la novela policial clásica en un cuento persa
PyCon Latam 2021
Mi tercera PyCon
Acabo de terminar mi charla en la PyCon Latam 2021, y fue una gran experiencia. A diferencia de otras oportunidades, estaba relajado y disfruté muchísimo dar la charla. Creo que la diferencia tuvo que ver con la preparación. Aunque ya sabía bastante, había aprendido muchísimo más en las últimas semanas preparando el tutorial de RISE que publiqué en el blog. Así que ya tenía todo el material a mano, y fue fácil armar las diapositivas.

En la PyCon utilizamos la plataforma hubilo.com que me gustó mucho, fue fácil de utilizar y no tuve complicaciones durante el evento. Un tremendo esfuerzo del equipo organizador, porque tuvieron que cambiarse a última hora de la plataforma que tenían considerada últimamente.

Algunas de las cosas que aprendí específicamente para esta presentación fueron:
- Porqué se llama RISE: Reveal.js IPython Slideshow Extension
- Utilizar
_repr_html_para tener una representación nativa para los jupyter notebook, - A ocultar los botones con
,, y a usar header y footer con propiedades de html. - A hacer animaciones más complejas con
<p class="fragment">Mi texto</p>, al igual que en revealjs - A ocultar código con un botón.
La presentación en sus distintas formas de visualización se encuentran acá: Slides pylatam
All systems red
Exploradores planetarios con un twist
Un libro divertido y fácil de leer. El personaje central es un robot humanoide, y tiene muchas características humanas: humor negro, desprecio por el trabajo, limitaciones de multitasking, procrastinación y adicción a las series televisivas. Me resulta divertida la idea de que la evolución de la tecnología termine produciendo características tan humanas y alejadas de lo perfecto. El nudo central del libro es un poco simplón, pero la narración está muy bien lograda.
Ocultando código en jupyter notebook
¿Cómo ocultar el código pero no el output en jupyter notebooks?
Contenidos
A veces quieres mostrar el resultado de una celda, pero no mostrar el código. Por ejemplo, con el código de un gráfico o de una tabla.
Hay 2 maneras de cumplir con este objetivo:
- Simple: creando función auxiliar y ejecutándola.
- No tan simple: usando un poco de magia de jupyter notebook.
Opción Simple:
Podemos definir la función en un archivo, y luego importarla y ejecutarla:
from mi_carpeta import mi_script
mi_script.mi_grafico(order=6)
Si es necesario, es posible mostrar el contenido del script o la función:
!cat mi_carpeta/mi_script.py
Opción 2:
Usando una función específica para ocultar código, creada con una mezcla de html y javascript.
def toggle_cell_code(button_id):
"""
Adds a button to toggle (show/hide) the code cell but not the output.
Parameters
----------
button_id : str
An identifier for cells that will hide/show when button is pressed.
"""
from IPython.display import display_html
my_html = '''
<button type="button" id="%s" onclick="code_toggle('%s')">Codigo</button>
<script>
function code_toggle(my_id) {
// get the parent element for the cell code and output
var p = $("#"+my_id);
if (p.length==0) return;
while (!p.hasClass("cell")) {
p = p.parent();
if (p.prop("tagName") =="body") return;
}
// get the cell code and toggle its value
var cell_code = p.find(".input");
cell_code.toggle();
}
</script>
''' %(button_id, button_id)
return display_html(my_html, raw=True)
Para usar esta funcionalidad, basta con incluirla en la celda del código (antes o después), para agregar el botón que permitirá ocultar y mostrar el código.
toggle_cell_code("un_string_unico_y_reconocible")
# Código para graficar
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
area = (30 * np.random.rand(N))**2 # 0 to 15 point radii
plt.scatter(x, y, s=area, c=colors, alpha=0.5)
plt.show()
Así se ve:

¿Cómo funciona?
Analicemos el código en python que ejecuta la función toggle_cell_code:
Primero:
def toggle_cell_code(button_id):
La función toma un único argumento: button_id.
Segundo:
from IPython.display import display_html
Tercero:
my_html = '''
Mucho código en html acá que analizaremos después...
''' %(button_id, button_id)
Este código define la variable my_html, en la cual se inserta 2 veces el valor button_id.
Cuarto:
return display_html(my_html, raw=True)
Finalmente, se regresa como el display del html creado mediante una función de IPython.display.
Analicemos ahora el código html:
Primero:
<button type="button" id="%s" onclick="code_toggle('%s')">Codigo</button>
El código anterior define un botón, con un identificador único (button_id) cuyo valor se define de manera dinámica (en el momento de ejecutar la celda del notebook). Al apretar el botón ejecuta la función code_toggle() con el mismo identificador (button_id) como argumento. Eso permite que la función en javascript sepa cuál es la celda en específico que debe ocultar.
Segundo:
<script>
function code_toggle(my_id) {
// get the parent element for the cell code and output
var p = $("#"+my_id);
if (p.length==0) return;
while (!p.hasClass("cell")) {
p = p.parent();
if (p.prop("tagName") =="body") return;
}
// get the cell code and toggle its value
var cell_code = p.find(".input");
cell_code.toggle();
}
</script>
Primero que nada, $ (jquery) permite encontrar los elementos en una página web, en este caso, el elemento asociado al identificador entregado (button_id). Luego se itera hasta encontrar el elemento que contiene la celda de código y el output (se identifica porque tiene clase "cell"). Luego se accede a la celda de código (que siempre la clase "input"). Finalmente, toggle muestra/oculta el botón.
Nota:
- Los identificadores tienen el caracter especial
# - Las clases tienen el carácter especial
.
Todas estas ideas son una compilación y simplificación de códigos de muchas otras personas. Algunos enlaces relevantes son:
Agregando un filtro en fastpages
Más personalización en fastpages 2
El menú superior del blog empezaba a tener demasiadas opciones, y tenía ganas de simplificarlo. Me dí cuenta que en realidad habían 2 grandes temáticas: lo que escribo y lo que leo. Lo que estoy leyendo tiene siempre el mismo formato: opinión de libro. Lo que escribo puede ser un post simple (en markdown) o algo más sofisticado en un notebok, o bien ser algún escrito de ficción. Lo que necesitaba era agregar botones con filtros para poder seleccionar según fuera necesario.
Antes de ponerme a modificar el html de la página web, hice una página web en local con html y css autocontenido,
que cumpliera con estas condiciones. Después de googlear un poco, aprendí que eso podía hacerse de manera simple con
las opciones display: 'none' y display: 'block'.

El código es el siguiente. Toda la magia está en usar javascript, donde la función querySelectorAll que permite encontrar todos los elementos de una cierta condición (las clases story, notebook y post). Para manipular más fácil creé una función que muestra una(s) clase(s) y esconde otra(s).
<!DOCTYPE html>
<html>
<head>
<style>
.story {background-color: green;}
.notebook {background-color: blue;}
.post {background-color: red;}
</style>
</head>
<body>
<button onclick="show_hide('.story','.notebook, .post')">Show Book</button>
<button onclick="show_hide('.notebook','.story, .post')">Show Notebooks</button>
<button onclick="show_hide('.post','.notebook, .story')">Show Posts</button>
<button onclick="show_hide('.story,.notebook, .post','')">Show All</button>
<div class="story"> <h1>Book</h1> </div>
<div class="notebook"> <h1>Notebook</h1> </div>
<div class="post"> <h1>Post</h1> </div>
<div class="notebook"> <h1>Notebook</h1> </div>
<div class="post"> <h1>Post</h1> </div>
<div class="story"> <h1>Book</h1> </div>
<div class="notebook"> <h1>Notebook</h1> </div>
<script type="text/javascript">
function show_hide(selector_show, selector_hide) {
if (selector_show.length>0){
elements = document.querySelectorAll(selector_show);
for (let i = 0; i < elements.length; i++){
elements[i].style.display = 'block';
}
}
if (selector_hide.length>0){
elements = document.querySelectorAll(selector_hide);
for (let i = 0; i < elements.length; i++){
elements[i].style.display = 'none';
}
}
}
</script>
</body>
</html>
¡Listo! Una vez que estaba funcionarlo, solo fue necesario agregarlo al layout correspondiente, creando las clases respectivas para cada tipo de post. ¡Me sorprendió que funcionó a la primera!
Como los botones se veian un poco feos, les puse el css de un botón de streamlit.
.css-qbe2hs {
justify-content: center;
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
@media (orientation: portrait) {
font-size: 75% !important;
}
margin-bottom: 10px !important;
margin-right: 10px !important;
line-height: 1.6;
color: inherit;
width: auto;
background-color: rgb(255, 255, 255);
border: 1px solid rgba(38, 39, 48, 0.2);
}
Finalmente, el resultado se ve muy profesional para lo sencillo del código:

Cambiando fontsize de código
Más personalización en fastpages
La configuración por defecto de fastpages para el código no es de mi agrado. Me molestaba que incluso cuando la línea no es muy larga se ocultaba gran parte del código.
Esto ocurre porque el tamaño de la fuente es mayor que la del texto.
Al mirar el css, se ve que los textos de código están definidos como el
105% o 110% del tamaño normal de la fuente.
Yo pensaría que debe ser al revés: el código está para ser mirado pero no analizado en profundidad.
Si lo desea, alguien descargará el código y lo revisará en su editor de peferencia.
Es mejor que se vea todo el código, a que haya que hacer scroll.
¿Qué tan difícil es el cambio?
Googleando llegué a la siguiente respuesta
donde se explica que es necesario editar el archivo _sass/minima/custom-styles.scss.
Agregué el siguiente código, para reducir el tamaño de la fuente al 75%.
code {
font-size: 75% !important;
}
pre {
font-size: 75% !important;
}
He visto que !important se desaconseja, pero bueno, podremos hacer una excepción en virtud del tiempo
y de la humildad de este blog.
Test final: el siguiente texto con 80 chars debería verse completo…
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
¡Funciona!
Videos en Jupyter Notebook
¿Cómo incrustar videos en markdown o jupyter notebook?
Poner un video en markdown o jupyter notebook es sencillo, pero hay varias opciones y no todas funcionan. ¡La opción que pongo acá funciona en todos los casos!
¿Cómo se ponen videos en un notebook?
Es posible usar un video en local (de tu disco duro) o en línea. Sin embargo, resulta aconsejable subir el video en línea y enlazar ese video. ¿Por qué? Porque así la ruta no cambia al ejecutarlo como notebook en local y al convertirlo mediante fastpages en página web.
Usaremos el video que usan de ejemplo en w3school: mov_bbb.mp4
Agregando el repositorio en github, obtenemos la ruta del archivo (notar que tiene que estar en version raw):
https://github.com/sebastiandres/blog/blob/master/videos/mov_bbb.mp4?raw=true
El código html para incrustar un video en html o markdown es:
<video controls src="path_to/video_name.mp4" width="800"></video>
Podemos usar el html en una celda de markdown:
También es posible usar código en python para incrustar el video como resultado de la ejecución de una celda:
from IPython.display import HTML
HTML("""
<video controls src="https://github.com/sebastiandres/blog/blob/master/videos/mov_bbb.mp4?raw=true" width="800">
</video>""")
Todas las opciones del tag <video> están en la
documentación.
Tutorial de RISE
Usando jupyter notebooks para presentaciones interactivas
Creo que existe poco material sobre cómo utilizar la librería RISE, especialmente en español. En esta serie de artículos dedicados espero poder contribuir a disminuir esa brecha.
Si encuentras errores o existe alguna funcionalidad no documentada, ¡no dudes en comentar!
¿Qué es RISE?
RISE es una extensión a los jupyter notebooks que permite transformarlos en una presentación interactiva. Toda las celdas pueden editarse y ejecutarse directamente, durante la presentación. Esto es práctico si necesitas corregir un error en una celda de texto. Más importante aún, puedes ejecutar código directamente en el kernel. En una misma diapositiva puedes tener múltiples celdas y elegir cuál ejecutar, o corregir el texto y volver a ejecutar.

¿Porqué es mejor?
Primero, porque simplifica enormemente la generación de material. El jupyter notebook es simultáneamente la presentación, el código, el apunte y texto oficial, y no necesitas actualizar en múltiples lugares. Detesto sacar una captura de pantalla a un trozo de código, porque tendré que estar eternamente actualizando esa imagen. También odio escribir código en cuadros de texto que quedan sin formato y donde siempre se pasa algún error, que no se detecta porque el código no se puede ejecutar.
Segundo, porque simplifica la distribución del material. La presentación contiene todo lo que la audiencia necesita: explicaciones, imágenes, videos y/o código. Proporcionando un archivo requirements.txt es fácil recrear un ambiente con las librerías necesarias para ejecutar el código. Almacenando el jupyter notebook en github, es además fácil compartir el archivo ipynb del jupyter notebook, un html o pdf pre-generado fácil de visualizar o incluso un enlace a un repositorio binder donde la presentación se puede volver a visualizar de manera interactiva. Entregar múltiples opciones le entrega facilidades a la audiencia y permite generar más impacto.
Sobre el tutorial
Como el jupyter notebook se transforma en una presentación, se heredan todas las funcionalidades, tanto de texto (markdown, latex) como de código (gráficos, display, widgets) del formato jupyter notebook. Probablemente ya conoces muchas de estas funcionalidades que no son de RISE. En el enlace podrás encontrar estas funcionalidades de jupyter notebook útiles para presentaciones interactivas.
En este tutorial se divide en tres partes:
- Aspectos básico: Cómo instalar la librería, cómo crear una presentación y cómo realizar una presentación exitosa.
- Aspectos intermedios: Cómo configurar el diseño en RISE y cómo compartir tu presentación.
- Aspectos avanzados: Cómo crear columnas, cómo realizar encuestas, cómo portar una presentación de powerpoint a jupyter notebook y otros consejos.
Enlaces:
- Documentación oficial de RISE.
- Charla en PyCon Colombia 2020 donde hablo de RISE y cómo hacer encuestas.
- Otras charlas y eventos donde uso librería RISE.
Nuevo favicon
De python a sebastiandres en 1 paso.
Ya había reemplazado el favicon del sitio, pero no me terminaba de convencer. Hoy mirando el logo de python me di cuenta que podía facilmente transformarse en una S, que me pareció muy adecuado para un favicon simple pero memorable.
![]()
La imagen final tiene algunos detalles en los bordes, pero no es relevante dado que al convertirlo a favicon se reduce enormemente el tamaño.
![]()
Hay varios servicios para generar un favicon a partir de una imagen, en este caso obtuve mejores resultados con favicon.io.
El favicon se puede modificar reemplazando directamente images/favicon.ico, o bien,
editando el archivo _includes/favicons.html para definir una ruta diferente.
Tutorial de RISE - parte 3
Aspectos avanzados para hacer presentaciones interactivas con jupyter notebooks
Contenidos
- ¿Cómo puedo configurar texto y columnas?
- Convierte tus presentaciones automáticamente
- Ocultando código
- ¿Cómo puedo hacer actividades interactivas?
- ¿Cómo hacer encuestas?
- Usa un teclado inalámbrico
Esta es la parte 3 de 3 del tutorial de presentaciones interactivas en jupyter notebook.
¿Cómo puedo configurar texto y columnas?
Como las celdas de markdown aceptan html, es posible usar todos sus trucos. Uno de éstos permite generar dos columnas, típicamente para tener una columna con texto y otra con una imagen (estática o gif).
Por ejemplo, el siguiente código en html se puede usar en celdas de markdown
<div>
<div style="display: inline-block; width: 40%;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
<div style="display: inline-block; vertical-align: text-top; width: 50%;">
<img src="2021-08-03-tutorial-rise-parte-3/columnas.gif" alt="LoreIpsum">
</div>
</div>
y proporciona la siguiente imagen:

Pero en general, hay que segur el consejo: keep it simple.
Gastar 1 hora en una slide de 1 minuto es una mala inversión.
Convierte tus presentaciones automáticamente
Uno de los primeros desafíos para comenzar a usar notebook + rise es convertir el contenido de las presentaciones que ya están en powerpoint. Para solucionar eso, creé un sitio que permite convertir una presentación powerpoint a jupyter notebook + rise de manera automática. Ya genera texto e imagen a 2 columnas, como es el caso habitual en diapositivas. No es perfecto, pero te ahorrará un buen tiempo de copiar y pegar texto.
La aplicación para convertir se encuentra en este enlace: pptx a rise
El funcionamiento es el siguiente. Supongamos que tienes una presentación que necesitas convertir: simple.pptx

Primero que nada, en el sitio debes cargar la presentación, esperar que se convierta y descargar el notebook + RISE equivalente:

Después de descargar y descomprimir, al lanzar el notebook en jupyter la presentación debería comenzar de inmediato:

Observación 1: El notebook que se obtiene es el punto de partida para editar la presentación. Puedes agregarle más contenido y código, y personalizar el diseño tanto como desees.
Observación 2: Si no lo has hecho antes, deberás instalar jupyter notebook y RISE, obviamente.
Opción Simple:
Podemos definir la función en un archivo, y luego importarla y ejecutarla:
from mi_carpeta import mi_script
mi_script.mi_grafico(order=6, figsize=(12,8))
Opción 2:
Usando una función específica para ocultar código, creada con una mezcla de html y javascript.
def toggle_cell_code(button_id):
"""
Adds a button to toggle (show/hide) the code cell but not the output.
Parameters
----------
button_id : str
An identifier for cells that will hide/show when button is pressed.
"""
from IPython.display import display_html
my_html = '''
<button type="button" id="%s" onclick="code_toggle('%s')">Codigo</button>
<script>
function code_toggle(my_id) {
// get the parent element for the cell code and output
var p = $("#"+my_id);
if (p.length==0) return;
while (!p.hasClass("cell")) {
p = p.parent();
if (p.prop("tagName") =="body") return;
}
// get the cell code and toggle its value
var cell_code = p.find(".input");
cell_code.toggle();
}
</script>
''' %(button_id, button_id)
return display_html(my_html, raw=True)
Y así se ve en el código:
toggle_cell_code("un_string_unico_y_reconocible")
# Código para graficar
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
area = (30 * np.random.rand(N))**2 # 0 to 15 point radii
plt.scatter(x, y, s=area, c=colors, alpha=0.5)
plt.show()
Así se ve:

hide_code()
print("Hmmm444")
¿Cómo puedo hacer actividades interactivas?
Para incluir actividades interactivas, como las que se pueden hacer con https://www.mentimeter.com/, https://kahoot.com/ o https://quizizz.com/ (entre otros), es posible tomar varios caminos.
Lo más simple es incluir una diapositiva con el enlace al que tiene que acceder los usuarios, idealmente con un código QR para facilitar el acceso desde smartphones. Luego simplemente te cambias a la página correspondiente para mirar los resultados y compartirlos en pantalla.
Otra opción es no salir del jupyter notebook e incrustar la página web de los resultados. Para eso, es posible usar IFrames en una celda de código. De esa manera, es posible embeber dentro del notebook y una diapositiva una página web con una actividad.
from IPython.display import IFrame
IFrame("https://es.wikipedia.org/wiki/Iframe", width=600, height=450)
Importante:
- Comparte un enlace y código QR del url al que debe acceder tu audiencia.
- En una diapositiva, mediante IFrame, muestra lo que tu audiencia debería ver en sus celulares o computadores.
- En otra diapositiva, mediante otro IFrame, coloca el resultado de la actividad interactiva que debería irse completando a medida que la gente responda.
Ahora bien, puede suceder que la página que desees incrustar con IFrames no lo permita, así que siempre realiza pruebas antes de tu presentación.
¿Cómo hacer encuestas?
Es muy importante recibir retroalimentación de la presentación para poder mejorar.
Recomiendo hacer 3 preguntas simples al finalizar toda presentación:
- ¿Qué te pareció la charla? Evaluar de 1 a 5.
- ¿Qué le agregarías a la charla?
- ¿Qué le sacaríias a la charla?
Esto puedes lograrlo embebiendo una encuesta de Google Forms, Microsoft Forms u otros mediante un IFrame.
from IPython.display import IFrame
IFrame("https://forms.office.com/Pages/ResponsePage.aspx?id=zu7OdUTRPU-clJ5rQCX8_4qs5cX1Y7dFhVdiCz848sBUOTVJWktZQlBVNVJDNTg4OVA1N1JaMVlSNS4u",
width=450, height=600)
Usa un teclado inalámbrico
En charlas y clases resulta cómodo tener un teclado y mouse inalámbrico. Esto permite que no tengas que estar cerca del notebook, de esa manera puedas moverte libremente por el escenario.
Resulta práctico también para crear la posibilidad que un asistente de la charla pueda editar una celda de código. ¡Esto es especialmente entretenido en una clase!
Tutorial de RISE - parte 2
Aspectos intermedios para hacer presentaciones interactivas con jupyter notebooks
Contenidos
- ¿Cómo puedo compartir la presentación?
- ¿Cómo configuro el diseño de RISE?
- ¿Cómo activo la pizarra?
- Importante consejo final
Esta es la parte 2 de 3 del tutorial de presentaciones interactivas en jupyter notebook.
Repositorio
No trabajes en local. Siempre es más conveniente ir versionando los archivos mediante git en un repositorio. Ese repositorio puede respaldarse en línea, en un proveedor como github, bitbucket, gitlab o tu u otro de tu preferencia. El proveedor github es una buena opción gratuita que tiene muchas integraciones y complementos.
Asegúrate de tener un archivo requirements.txt que contenga las librerías que se requieren para poder ejecutar las distintas celdas de código de tu presentación. No olvides incluir todo el código, datos e imágenes.
Resulta útil tener un archivo README.md con la descripción del repositorio, y enlaces a las distintas opciones de visulización para el archivo.
En github, para un usuario nombre_usuario en un repo nombre_repo, puedes además crear una página index.html , y de esa manera al activar "Gitub Pages" la página estará accesible en https://nombre_usuario.github.io/nombre_repo/. De manera más general, un archivo en la ruta /ruta_carpetas/nombre_archivo.html se visualizará en https://nombre_usuario.github.io/nombre_repo/ruta_carpetas/nombre_archivo.html.
Yo he diseñado un template con un formato fijo que reutilizo para las distintas presentaciones, y que así se ve para una de mis charlas (Pycon Colombia 2020):

Creé el formato para para emular a linktree, de manera de reutilizarlo con mínimo esfuerzo. Escribí al respecto en este post.
mybinder
Binder es un servicio que permite lanzar un jupyter notebook en el navegador y en la nube, sin consumir recursos de tu computador y de manera completamente gratuita. Resulta práctico para que otras personas pueden ver la presentación de manera interactiva sin tener que instalar nada en su computador, ¡incluso desde un smartphone o tablet!
Para usar mybinder, debes tener tu repositorio en github.
En Binder puedes introducir los distintos parámetros:
- Usuario github: nombre_usuario
- Nombre repositorio: nombre_repositorio
- Nombre de la rama del repositorio: nombre_rama
- Ruta al archivo: ruta_carpetas
- Nombre del archivo: nombre_archivo.ipynb
- En la rama, en el directorio principal, debe existir el archivo
requirements.txt
Al acceder a la siguiente página se ejecutará el jupyter notebook, incluyendo la extensión RISE si la colocaste en los requirements.txt:
https://mybinder.org/v2/gh/nombre_usuario/nombre_repositorio/nombre_rama?filepath=ruta_carpetas/nombre_archivo.ipynb
Exporta a html
Para generar un html funcional de las diapositivas, tienes que hacer en el terminal:
jupyter nbconvert --to slides ruta_carpeta/nombre_archivo.ipynb
donde ruta_carpeta/nombre_archivo es, por supuesto, la ruta a tu notebook-presentación.
Se generará un archivo html estático de tu presentación, con el nombre ruta_carpeta/nombre_archivo.slides.html.
Esta página web en html puedes visualizarse en el navegador sin mayores dependencias, pero donde no podrás editar o ejecutar las celdas. Si ya realisaste tu presentación, puedes guardarla en el repositorio como un respaldo adicional.
Exporta a pdf
Para generar la presentación en pdf se requieren 3 pasos:
-
Genera las diapositivas en html, usando nbconvert, para que queden "activas":
jupyter nbconvert --to slides ruta_carpeta/nombre_archivo.ipynb --post serveSe abrirá una página web en el navegador, en la dirección
http://127.0.0.1:8000/nombre_archivo.slides.html#/ -
Edita la ruta, para que sea
http://127.0.0.1:8000/nombre_archivo.slides.html?print-pdf. Nota que hay que reemplazar#/por?print-pdf. La página mostrará ahora las diapositivas de manera vertical. -
Finalmente, guarda la página web como pdf. Puedes jugar con distintas opciones según necesites (sin márgenes, activar imágenes de fondo, etc.)
La documentación oficial para exportar a pdf está acá.
La metadata es un diccionario json con las distintas opciones de todo el notebook. Por defecto, no trae ninguna opción cargada de RISE. Por eso, tenemos que agregar las opciones que se quieren configurar:
"rise": {
"opcion_1": valor_opcion_1,
...
"opcion_n": valor_opcion_n,
}
`
Observación importante:
Cada vez que editas la metadata, es necesario cerrar (apagar) el notebook y volver a abrirlo para que se recarguen las opciones.
Lanzar RISE de manera automática
Permite que al activar un jupyter notebook, este se ejecute en modo presentación de manera automática. ¡Opción ampliamente recomendada!
En la metadata del notebook se agrega la opción de "autolaunch" con valor true (todo en minúsculas, ¡es verdadero de javascript, no de Python!):
"rise": {"autolaunch": true}
`
De esta manera, si publicas tu jupyter notebook con mybinder.org, se mostrará en modo presentación automáticamente (audiencia no necesitará saber donde está el botón de “iniciar presentación", por ejemplo si lo compartes en mybinder).

Eligiendo un tema
El tema de la presentación controla el aspecto general de la presentación.
En la metadata del notebook se agrega la opción de "theme" con el nombre del tema deseado:
"rise": {"theme": "sky"}
`
Existen 11 opciones, que se heredan de la librería revealjs:
- black: Fondo negro, letras blancas, links azules.
- white: Fondo blanco, letras negras, links celestes.
- league: Fondo gris, letras blancas, links celestes.
- sky: Fondo celeste, letras oscuras, links azules.
- beige: Fondo beige, letras oscuras, links cafés.
- simple: Fondo blanco, letras negras, links azules.
- serif: Fondo café, letras grises, links cafés.
- night: Fondo negro, letras blancas, links naranjos.
- blood: Fondo oscuro, letras blancas, links rojos.
- moon: Fondo azul oscuro, letras grises, links azules.
- solarized: Fondo blanco crema, letras verde oscuro, links azules.
Eligiendo una transición
El tipo de transición entre slides se define de manera similar. En la metadata del notebook se agrega la opción de "transition" con el nombre de la transición deseada:
"rise": {"transition": "zoom"}
Las opciones, heredadas de las transiciones existentes en la librería revealjs, son:
- none: Sin animación.
- fade: Animar con Cross fade — default for background transitions
- slide: Slide between backgrounds — default for slide transitions
- convex: Animar con ángulo convexo.
- concave: Animar con ángulo convexo.
- zoom: Animar para que la diapositiva siguiente crezca desde el centro de la pantalla.
El siguiente gif muestra cada transición:

Definiendo una imagen de fondos
Simplemente, usar:
"rise": {
"backimage": "mybackimage.png",
}
}
Esta opción es útil si quieres un fondo personalizado, como a veces se exige en algunos congresos. Como es habitual al manejar imágenes, conviene usar archivos png con fondo transparente para que se integre bien con el theme elegido.

Durante la presentación se puede utilizar la pizarra haciendo click en los íconos, que permiten dibujar en una pizarra o sobre las diapositivas.
Es posible cambiar de color de lapiz presionando s y q. Se puede borrar la pizarra con -.

Las anotaciones se preservan incluso al cerrar el modo presentación. Sin embargo, no se guardan al cerrar el notebook.
Importante consejo final
Tantas opciones pueden ser sobrecogedoras y difíciles de recordar. Como no se puede tener comentarios en un json, resulta práctico tener guardadas todas las opciones y simplemente agregar un _ antes de las opciones que no se desean usar.
En mi caso, tengo el siguiente json en todos mis notebooks con lo cual no tengo que memorizar nada, y sólo adapto según necesito.
"rise": {
"autolaunch": true,
"enable_chalkboard": true
"theme": "black",
"transition": "zoom",
"_header": "<h1>PRESENTATION NAME // EVENTs</h1>",
"_footer": "<h3>NAME, DATE</h3>",
"_backimage": "path/to/mybackimage.png",
"_theme_options": [ "black", "white", "league", "beige",
"sky", "night", "serif"],
"_transition_options": ["none", "fade", "slide", "convex", "concave", "zoom"],
}

Tutorial de RISE - parte 1
Aspectos básicos para hacer presentaciones interactivas con jupyter notebooks
Contenidos
- ¿Qué es RISE?
- ¿Qué contenido puedo poner?
- ¿Cómo instalar?
- ¿Cómo configurar lo que está en cada diapositivas?
- ¿Cómo moverse por las slides?
- ¿Cómo se configuran las diapositivas?
- ¿Qué opciones hay?
- ¿Se puede editar durante la presentación?
- ¿Cómo controlo el tamaño?
- ¿Dónde están las notas del presentador?
- Comparte tu estructura
- Gráficos
- Apoyos gráficos
Esta es la parte 1 de 3 del tutorial de presentaciones interactivas en jupyter notebook.
¿Qué es RISE?
RISE es una extensión a jupyter notebook que, en lugar de desplegar las celdas en una larga página web, las despliega en una presentación usando la librería de javascript reveal.js.
Sigue siendo una página web (al igual que el notebook), pero las celdas se agrupan en diapositivas.
¿Qué contenido puedo poner?
Puedes mezclar contenido usando las celdas de markdown y código, según necesites:
- Markdown: texto, latex, imágenes, tablas, etc.
- Código: código, gráficos simples o interactivos, videos, sonido, iframes, javascript, entre otros.
Como regla general, si se muestra correctamente en el notebook, se verá bien en la diapositiva. ¡Sé creativo!
Eso hará que se agregue el botón de iniciar presentación (destacado en rojo).

También puedes agregar rise a tu archivo requirements.txt para que se instale automáticamente al generar un ambiente.
En caso de tener problemas, revisa los detalles adicionales de instalación.
¿Cómo configurar lo que está en cada diapositivas?
Paciencia, se requiere todavía un paso adicional.
En el menú de jupyter notebook, es necesario seleccionar View/Cell Toolbar/Slideshow para que permita configurar el tipo de celda para diapositiva.
Esto se requiere porque a cada celda del notebook se le agregará metadata para saber en que diapositiva debe ir (o si se debe saltar).

Eso dejará todo configurado para poder seleccionar el tipo de celda respecto a la diapositiva.

¿Cómo moverse por las slides?
Al hacer click en el botón "Iniciar presentación", la presentación se iniciará en la celda que esté activa.
- Se accede a la próxima diapositiva o fragmento con
Espacio(o la flecha derecha). - Se retrocede a la diapositiva o fragmento anterior con
Shift Espacio(o la flecha izquierda). - Se avanza a la proxima sub-diapositiva con
Page Up. - Se retrocede la sub-diapositiva anterior con
Page Down.
Una diferencia de una presentación típica de PowerPoint es que existen 2 dimensiones: las diapositivas (slides) que avanzan de izquierda a derecha como es tradicional, pero también sub-diapositivas (subslides) que son slides opcionales y que avanzan de arriba a abajo.

Observación: En general, es dificil recordar el orden de las slides y sub-slides. Yo personalmente nunca uso sub-slides por esta razón y prefiero solo tener orden "horizontal".
¿Cómo se configuran las diapositivas?
Existen varios tipos de celda con distintas funcionalidades:
-
-: valor por defecto. La celda se muestra con la slide anterior. -
Slide: inicia una nueva diapositiva (dirección horizontal). -
Sub-slide: iniciar una nueva sub-diapositiva (dirección vertical). -
Fragment: se concatena a la celda anterior, pero no se muestra inmediatamente. -
Skip: no se muestra la celda en las diapositivas. -
Notes: No se muestra en las diapositivas, sólo se muestra en las notas para el presentador.
¿Qué opciones hay?
Existen múltiples funcionalides accesibles con el teclado durante la presentación, pero las principales a recordar son:
-
?: ver todos los shortcuts. -
,: ocultar los botones. -
\: poner la pantalla en negro. Útil para discutir algo sin distracciones visuales. - ``:
Las funcionalidades se controlan con los siguientes botones:

¿Cómo controlo el tamaño?
Un problema común es que al conectar el computador a otra pantalla o datashow, no se alcanza a ver en la diapositiva todo el código, texto o imagen.
Lo único que debes hacer es usar usar Ctrl + y Ctrl - para regular el tamaño (Command +y Command - en Mac), de la misma manera que regulas el tamaño de una página web.

Comparte tu estructura
Cuando se ejecuta código en una presentación, es una buena práctica mostrar la estructura de carpetas y los archivos con los que se va a trabajar. Eso ayudará a despejar dudas de cómo está funcionando el código.
Esto se puede lograr fácilmente con comando mágicos (como %ls) o ejecutando código en bash (como !ls).
La diferencia entre ambos radica en lo siguiente:
- Los comandos mágicos son específicos y definidos por cada kernel. Pueden existir comandos que estén en python pero no en R.
- Los
!permiten ejecutar instrucciones en el terminal y es más flexible. Uno de los más comunes es ofrecer instalar las librerías, como:pip install rise matplotlib
%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np
with plt.xkcd():
fig = plt.figure(figsize=(14,6))
x = np.linspace(-5,5,num=100)
y = np.abs(np.abs(np.sin(2*x)/x))
plt.plot(x,y)
Apoyos gráficos
Me ha servido mucho insertar gifs en las presentaciones. Un buen gif animado es un buen compromiso entre una imagen y una película, y sirve para tener una animación que ilustre algun proceso. Existen muchos programas para grabar gifs. Una solución que me ha funcionado bien para animar gifs simples es crear un diagrama mediante una animación en PowerPoint, y después grabar un gif considerando apropiadamente los tiempos.
Eso concluye la primera parte 1 del tutorial de presentaciones interactivas en jupyter notebook.
Tutorial de RISE - Aspectos preliminares
Funcionalidades de jupyter notebooks útiles para presentaciones interactivas
Nota: Este documento es el preámbulo de la entrega por partes del tutorial de RISE.
APUNTES:
- SONIDO
- VIDEO
- Widgets
Uniendo archivos Excel
Automatizando unir archivos históricos
Contexto
Hoy me pidieron unir varios archivos excel en un único archivo. Tomé las siguientes convenciones:
- Los archivos excel están en la carpeta
in, y tienen las mismas columnas en el mismo orden. - La carpeta
outtiene contendrá 2 archivos: un arhivo excel con la concatenación de todos los archivos, y una versión donde se mantuvo un único registro por rut (el último).
import pandas as pd
from glob import glob
# Leer todos los archivos excel de la carpeta in
all_files = glob("in/*.xlsx")
# Get columns
df = pd.read_excel(all_files[0])
columns = df.columns
# Unir los archivos en una lista
df_list = []
cols_list = []
for in_file in all_files:
print(in_file)
df = pd.read_excel(in_file)
df.columns = columns # Renombrar las columnas, mantener la convención del primer archivo
df["Archivo"] = in_file
df["#Fila"] = df.index
df_list.append(df)
# Concatenar
df_master = pd.concat(df_list)
# Sacando duplicados considerando único rut
df_last = df_master.drop_duplicates(subset="RUT COMPLETO", keep='last')
# Guardar en excel, cada resultado en una pestaña
writer = pd.ExcelWriter('out/Franquicia_joined.xlsx', engine='xlsxwriter')
df_master.to_excel(writer, sheet_name="todos", index=False)
df_last.to_excel(writer, sheet_name="ultimo_segun_Rut", index=False)
writer.save()
Easter Eggs
Customizando fastpages.
Para conocer un poco más del funcionamiento de fastpages, decidí personalizar un poco el sitio. Tenía ganas de agregarle algo divertido, así que me decidí por agregarle un toque absurdo a lo Monty Python.
404
La página 404 (página no encontrada) fue fácil de personalizar.
Bastó editar el archivo _pages/404.html, y agregarle una nueva imagen.
Pie animado
Un desafío mayor fue agregar un pie animado que se moviera sin desplazar otros elementos. Para ello, tuve que generar un css específico:
img.monty-python-foot {
position: absolute;
top: -900px;
z-index: 99;
animation: move_down 4s;
}
@keyframes move_down {
0% { transform: translateY(-1200px); }
50% { transform: translateY(800px); }
100% { transform: translateY(-1200px);
}
}
Luego, en las páginas donde quisiera aplicarlo, bastaba con agregar una imagen con la clase respectiva:
<img src="/blog/images/monty_python_foot.png" height=800px class="monty-python-foot" />
El resultado puede verse en algunas de las páginas del sitio, es cosa de buscar.
Experiencias del Sprint de Data Umbrella
Lo que aprendí en mi primer sprint contribuyendo a código abierto.
Desde hace mucho tiempo tenía el deseo de contribuir “en algún momento” a un proyecto de código abierto. Sin embargo ese momento ideal siempre era pospuesto para más adelante, cuando tuviera más experiencia y más tiempo. Por eso, cuando leí sobre el Sprint de Data Umbrella en varios canales de Telegram de python y programación, no dudé y me inscribí. Era la oportunidad perfecta para obligarme a aprender. No hay nada como un poco de presión social para mover los engranajes de un procrastinador.
Para ser sincero, no ubicaba a Data Umbrella. Se trata de una organización que se preocupa de proporcionar apoyo a grupos poco representado, ya sea por género, raza, edad, orientación sexual u otros, en los campos de Machine Learning, Data Science e Inteligencia Artificial. El sprint que realizaron el 26 de Junio tenía como foco Latinoamérica, que tiene una participación baja en estos temas. El trabajo de Data Umbrella con los grupos de poca representación resulta muy valioso para derribar todos los mitos y barreras de entrada que pueden estar frenando la llegada de nuevos talentos.
Lo que más me gustó del Sprint de Data Umbrella fue la organización: tenían un checklist muy preciso de los temas a revisar, con videos explicando cada paso a paso. Por eso, era fácil estimar cuánto tiempo necesitabas preparando o aprendiendo antes del sprint. El uso de discord también ayudó mucho a darle una informalidad y aspecto comunitario, y sirvió para responder las preguntas y conocernos. Introducirse en nuevo grupo siempre es difícil, y para novatos el desafío es aún mayor. El hecho de tener un pre-sprint y post-sprint ayuda a consolidar el aspecto humano y comunitario, resolver los problemas técnicos que siempre aparecen, y ganar confianza.
Durante el sprint, el hecho de organizarnos para programar de a pares también fue de gran ayuda. Con Leonardo Rocco trabajamos en 2 issues:
- DOC Ensures that ARDRegression passes numpydoc validation #20381
- DOC ensures FastICA estimator pass the numpydoc validation #20405
¡Puedo decir con orgullo que ambos pull requests ya fueron aceptados!
Al reflexionar sobre mi experiencia en el Sprint, me doy cuenta que tenía la expectativa que me faltaban muchas cosas por aprender.

En el sprint aprendí que no se requiere ser un super-programador para contribuir al código abierto. La realidad es que, de partida, no existe una única manera de contribuir. Hay un abanico interminable de posibles trabajos, desde los más sencillos a los más avanzados, y un largo camino de aprendizaje. Por eso, es importante darse cuenta que no se trata de que “no sabes” sino que “no sabes aún”, y que hay una comunidad dispuesta a apoyarte en ese proceso de aprendizaje. Todos estamos en un proceso de aprendizaje. Involucrarse en proyectos colaborativos es precisamente una manera de acelerar el aprendizaje, y de paso, contribuir a las librerías que más usas.

Hay un excelente informe del sprint en el blog de Reshama. La distribución de los participantes por países es bastante sorprendente. Imaginaba una distribución más uniforme, pero la mayoria de los participantes son de Argentina y Brasil. ¡Hay hacer algo al respecto!
Además de las contribuciones comunitarias e individuales, otro elemento importante del código abierto son las subvenciones y la financiación por parte de empresas. ¡Pídele a tu jefe presupuesto para financiar las herramientas que usas a diario! En particular, este sprint fue financiado en parte por una subvención de Code for Science & Society. Esto es comunidad y transparencia en estado puro: puedes obtener todos los detalles de la subvención en línea: Grant number GBMF8449 en Gordon and Betty Moore Foundation.
Data Umbrella Sprint - My Experience with open source
Things I learned from my first open source sprint and pull request
For a long time I wanted to contribute “at some point” to an open source project. That ideal moment was always postponed until later, when I had more experience and more time. So when I was read about the Data Umbrella Sprint on several telegram channels related to python and programming, I didn’t hesitate and signed up. It was the perfect opportunity to force myself to learn. There’s nothing like a little social pressure to push the gears of a procrastinator.
To be honest, I didn’t knew Data Umbrella. It’s an organization that is concerned with providing support to underrepresented groups, whether by gender, race, age, sexual orientation or otherwise, in the fields of Machine Learning, Data Science and Artificial Intelligence. The sprint held on June 26th had a focus on Latin America, because of the low participation in these topics. Data Umbrella’s work with underrepresented groups is very valuable in breaking down all the myths and entry barriers that may be holding back new talents.
What I liked most about the Data Umbrella Sprint was the organization: they had a very precise checklist of the topics to review, with videos explaining each step by step. It was easy to estimate how much time you needed to prepare or learn before the sprint. This really helped assuring you would have all the required knowledge for the sprint. The use of discord also helped a lot to provide a community and informal aspect, and served to answer questions and get to know each other. Getting into a new group is always difficult, and for newbies the challenge may be even greater. Having a pre-sprint and post-sprint helps to consolidate the human and community aspect, solve the technical problems that always arise, and gain confidence.
During the sprint, we worked with pair-programming and proved to be of great help. With Leonardo Rocco we worked on 2 issues:
- DOC Ensures that ARDRegression passes numpydoc validation #20381
- DOC ensures FastICA estimator passes the numpydoc validation #20405
I can proudly say that both pull requests have been accepted!
Reflecting on the sprint experience, I realize that I had the expectation that I had too many things to learn before doing anything useful to open source.

In the sprint I learned that you don’t need to be a super-programmer to contribute to open source. The reality is that, to begin with, there is no single way to contribute. There is an endless range of possible jobs, from the simplest to the most advanced, and a long learning curve. Therefore, it is important to realize that it is not that “you don’t know” but that “you don’t know yet“, and that there is a community willing to support you in that learning process. We are all in a learning process, from newbies to seasoned coders. Getting involved in collaborative projects is precisely a way to accelerate learning, and in the process, contribute to the libraries you use the most.

There is a great report of the sprint on Reshama’s blog. The participant’s distribution by country is quite surprising. I would have guessed a more uniform distribution, while it’s largely concentrated on Argentina and Brasil. We will have to do something about that!
Besides community and individual contributions, another big piece on open code are grants and funding from companies. Go ask your manager for budget to financing the tools you use day-to-day! In particular, this sprint was funded in part by a grant from Code for Science & Society. Community and transparency at its best: you can get all the details for the grant online: Grant number GBMF8449 at Gordon and Betty Moore Foundation.
Fastpages 2: reloaded
Las grandes ideas nunca mueren.
Ya perdí la cuenta de la cantidad de veces que he armado un sitio web para publicar posts y jupyter notebook. Esta vez espero ser más constante. Estuve averiguando sobre como funciona, y acá aprovecho de poner mis notas.
¿Cómo funciona fastpages?
Al crear un blog con fastpages se generan 2 ramas:
- En la rama master se encuentran los posts en formato markdown, jupyter notebooks o word, los archivos de configuración, y distintos archivo de html, css y javascript.
- En la rama gh-pages
se encuentra únicamente el html que se genera
a partir del contenido de la rama master, y que se despliega como
una página web en
<username>.github.io/<repository-name>gracias a Github Pages (sino, se vería como el clásico repositorio de código).
Cuando haces push de un cambio a master, se ejecutan automáticamente 3 acciones mediante github actions:
check_config.yaml: Revisa que los archivos de configuración sean consistentes y tengan la información necesaria. Esto toma usualmente unos 15 segundos.ci.yaml: Toma unos 90 segundos en completar los siguientes pasos.- Crea un ambiente linux donde se realizarán todas las operaciones.
- Chequea que tengas permiso hayas definido
SSH_DEPLOY_KEYS - Descarga la ultima versión del repositorio (la versión que recien pusheaste) de la rama master.
- Convierte a markdown todos los archivos de jupyter notebook y word.
- Descarga el tema de Jekyll
minimapara tener todos los html, css y javascript necesarios. - Usa Jekyll para convertir todos los markdowns a páginas web con el tema
minima. - Copia el html generado a la rama gh-pages.
gh-pages.yaml: Revisa que todo se haya generado correctamente (creo).
Personalizando la página principal
Para personalizar la página principal, basta con modificar ./index.html.
La página principal que se verá en el sitio web se genera de manera encadenada.
En index.html se define que se usará el layout home.html,
que a su vez define que usará el layout default.html.
Es posible modificar ./layouts/home.html tomará el texto de ./index.html y lo convertirá
de markdown a html, y le agregará un contenido adicional.
Personalizando el favicon
El favicon se puede modificar reemplazando directamente images/favicon.ico, o bien,
editando el archivo _includes/favicons.html para definir una ruta diferente.
Es posible generar un favicon de manera gratuita en realfavicongenerator.
Agregar nuevas páginas
Para agregar nuevas páginas, basta con agregar los archivos en la carpeta _pages.
Los archivos agregados pueden ser html o markdown, pero tienen la misma estructura:
---
layout: default
permalink: /desired-path/
title: Desired Name
search_exclude: true
---
SOME TEXT IN HTML OR MARKDOWN
El layout debe ser default o uno de los layouts definidos en la carpeta _layouts.
Se puede usar html o markdown, según lo que sea más conveniente usar.
Microcuentos tecnología
Cuentos enviados a concurso uPlanner en 100 palabras
Todo cobra sentido
Esperando el metro, escuchó una conversación entre dos estudiantes. ¿Porqué elegiste esa U? - preguntó uno. Y el otro respondió: ¿No cachai? Usan uPlanner.
Nica
Tu perfil y antecedentes laborales son impecables, serías un buen fit para Customer Success. Le di las gracias a la entrevistadora, y pasé a despedirme de Nicolás Diaz, que me había reenviado la oferta laboral. Me despedí también de Nicolás Walters y Nicolás Mena, esperando haber causado una buena impresión. Al día siguiente me llamaron. Lo siento -me dijo Nicole Lorenzini- pero ya tenemos un Nicolás González, y sería demasiado confuso para todos.
Siglas
El PM le comentó al PO que el reporte PBI de NPS estaba fallando por un problema del ETL, cuando el SQL no conectaba a la VPN. El PO había estado en JIRA, loggeando el MVP de GDPR para SaaS. El PM le pidió al PO no olvidar su OKR para aumentar el ARR mediante un mejor SEO para SMB. El CEO les recordó el valor de la cooperación, o los mandaría a la CTM mediante una expedita PLR, ¿OK?
Emulador de Linktree
Imitando un directorio de enlaces sólo con recursos gratuitos
Un directorio de enlaces permite tener entregar un link único y memorable, que puedes actualizar posteriormente a haberlo compartido. Donde, por ejemplo, al dar una charla pudiera decir “el código y la presentación estarán en este link”, pero donde posteriormente sea posible agregarle la grabación de la charla o un resultados de una encuesta. Probé con Linktree, pero pronto lo llené con demasiados enlaces y se volvió confuso y difícil de navegar. Necesitaba una forma de agrupar de manera temática y con un grado flexible de niveles de profundad. En efecto, si a alguien no le interesa el tema “X” probablemente no le interesaría el video de “X”, el código de “X”, la presentación de “X” en html o pdf.

Formato ofrecido por linktree (izquieda) versus lo que necesitaba y que tuve que crear (derecha). Necesitaba una manera de tener un directorio de enlaces con un nombre memorable, fácil de compartir y personalizar, pero permitiendo mantener distintos niveles de agrupación. Desafío adicional: Cuando se trata de recursos en línea, soy un tacaño extremo. Así que buscaba una solución que pudiera implementar sin costos mensuales recurrentes.
Como tacaño extremo llegué a pensar en hacer varias cuentas en linktree y vincularlas entre sí, pero no era una solución escalable (y tendría que acordarme de que correo y password usé para cada enlace). Así que entre una mezcla de vergüenza y flojera terminé desechando la idea. La solución me llegó de golpe. Github permite mostrar páginas html estáticas. ¡Podía hacer una página html simple que emulara a linktree y que podía guardar en cada repositorio en github! El enlace central seguiría siendo en linktree (lintr.ee/sebastiandres en mi caso) y los enlaces serían a los htmls plano de cada repositorio de github. Es una buena mezcla, porque linktree es bastante amigable y tiene algunas herramientas de administración como activar/desactivar enlaces. Comencé revisando el código de linktree, pero era excesivamente largo y confuso. Así que puse manos a la obra y me puse a construir una solución de manera incremental.
- Primera versión: Un único archivo html, simple y funcional. Imita los colores y botones sólo con html y css, sin javascript, incluyendo la animación del botón. Comienzo a desplegarlo en mis repositorios y enlazarlos desde linktree. Basta con activar cada repositorio como github pages para que github proporcione un enlace fijo y estable para index.html (o README.md). No es perfecto, pero funciona, ya se cumple el funcionamiento mínimo. Problemas detectados: el tipo y tamaño de fuente es similar pero no es correcto. El despliegue en un smartphone es muy distinto entre linktree y imitación (tacaña).

-
Segunda versión: Después de googlear, aprendí a manejar css selectivo usando @media (orientation: landscape/portrait). Problemas detectados: Todavía se ve un poco diferente, sobre todo en los márgenes y tamaño del botón. Al ir actualizando en mis repositorios esta segunda versión, me doy cuenta que es difícil recordar y distinguir visualmente cuál es la versión actual. Sería interesante tener algún tipo de indicador visual al respecto. Tercera versión: Ajustes a las propiedades del css para imitar mejor linktree (width, height, text wrapping, font sizes). Incluye un texto con la versión para distinguir la versión utilizada en cada repositorio y hacer más fácil la actualización. Problemas detectados: Los textos no están centrados adecuadamente. Lo peor es tener que ir copiando manualmente el nuevo estilo a cada repositorio en cada nueva actualización. Lo ideal es que el estilo se manejara centralmente y actualizara automáticamente a todos los repositorios que lo utilizaran.
-
Cuarta versión: En lugar de tener el css directamente en el html, lo muevo a un archivo css. De esa manera, todos los archivos pueden llamar a ese archivo. Eso permite separar contenido de formato. Esta cuarta versión me dio muchos problemas. En local todo funcionaba bien, pero github no desplegaba bien la página por un problema con el MIME type del archivo css. Así que tuve que usar https://raw.githack.com para obtener un enlace del css con el MIME type correcto. Problemas detectados: La actualización de css con github a través de https://raw.githack.com tiene un lag importante, a veces de horas, por lo que es difícil (y frustrante) comprobar si el cambio resultó bien.
-
Quinta versión: Comienzo a utilizar DigitalOcean para levantar un sitio estático a partir del repositorio en github. De esa manera, se generan urls fijas de los assets (css o eventualmente javascript) que se actualizan automáticamente con cada push a github, sin costo asociado y con el MIME type adecuado. Eso permite tener un enlace estable para css y html que puede utilizarse en los repos de github. Ahora también externalizo la “versión” del archivo, para que sea importado por el archivo. Estos archivos css y html ahora se llamarán siempre “latest_style.css” y “latest_copyright.html”, y que actualizo con cada cambio de versión. Así el archivo final se actualiza automáticamente cuando hay algún cambio (¡¡¡no más cambios masivos en los repos!!!). Se definen también algunas clases adicionales para manejar títulos y subtítulos. Problemas detectados: En general se observa bien, pero hay algunos glitchs de tamaño de fuente, textos centrados y detalles así.
* Sexta (y última) versión: Revisión general del código para optimizarlo y limpiarlo. Se eliminan algunas propiedades css innecesarias. Hay 3 archivos: El archivo principal es index.html donde se definen los enlaces, y se utilizan los archivos latest_style.css (estilo) y latest_copyright.html (versión de emulador). Cada nueva página con enlaces sólo necesita definir los enlaces, ¡el estilo y copyright ya está definido! Problemas detectados: Nada por ahora.

Sneak peak:
¿Cómo es el código? La primera versión mezcla el código y el estilo. Hace que actualizar múltiples archivos sea tedioso y propenso a errores (pero fue una versión rápida de desarrollar y autocontenida).
<!DOCTYPE html>
<html>
<head>
<style>
html {background-color:#3d3b3c; font-family: Karla, sans-serif;
font-size: 16px; font-weight: 700;}
h3, h2, h1 { color:#ffffff; margin-bottom: 16px; text-align: center;}
div {position: fixed; top: 25%; left: 50%;
-webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);}
a {text-decoration: none!important;}
p {background-color:#ffffff; color:#3d3b3c; line-height: 56px; width: 676px;
border: 2px solid rgb(255, 255, 255); margin-bottom: 16px; text-align: center;}
p:hover {background-color:#3d3b3c; color:rgb(255, 255, 255);}
</style>
</head>
<body>
<div>
<h1>Emulador de Linktree</h1>
<h3>Enlaces</h3>
<a href="https://linktree-ixkge.ondigitalocean.app/demo1.html" target="_blank">
<p>Primera versión</p>
</a>
<a href="https://linktree-ixkge.ondigitalocean.app/demo6.html" target="_blank">
<p>Última versión</p>
</a>
<h3>Enlaces</h3>
<a href="https://linktr.ee/sebastiandres">
<p>⇦ linktree</p>
</a>
</div>
</body>
</html>
La última versión permite cambiar el estilo y copyright de manera centralizada, y preocuparse únicamente del contenido.
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Karla:wght@300;400;600;700&display=swap" rel="stylesheet">
<link href="https://linktree-ixkge.ondigitalocean.app/latest_style.css" rel="stylesheet">
</head>
<body>
<div>
<h1>Emulador de Linktree</h1>
<h3>Enlaces</h3>
<a href="https://linktree-ixkge.ondigitalocean.app/demo1.html" target="_blank">
<p>Primera versión</p>
</a>
<a href="https://linktree-ixkge.ondigitalocean.app/demo6.html" target="_blank">
<p>Última versión</p>
</a>
<h3>Enlaces</h3>
<a href="https://linktr.ee/sebastiandres">
<p>⇦ linktree</p>
</a>
</div>
<!-- Version - dont change this code-->
<object data="https://linktree-ixkge.ondigitalocean.app/latest_copyright.html" width=100%></object>
</body>
</html>
¿Y cómo se ve todo al final? Se ve así:

Aprendizajes:
La separación de contenido y forma es esencial para poder ahorrar trabajo. Incluso para un proyecto pequeño como éste, ayuda enormemente a no duplicar trabajo y hacer más fácil las actualizaciones de N páginas.
La primera versión, que tenía el 80% de las funcionalidades me tomó el 20% del tiempo invertido. Pero el 80% del tiempo restante me enseñó muchas cosas que no sabía de html y css, mime types, y fue muy valioso.
Crear Github pages por cada repositorio es muy práctico, pero tiene ciertas limitaciones para que la gente no abuse al crear páginas estáticas y sea utilizado como un webserver gratis. Existen algunos sitios para entregar css y javascript con el MIME type correcto (https://raw.githack.com por ejemplo), pero no se actualizan automáticamente. DigitalOcean permite levantar sitios estáticos a costo cero y con actualización automática, y poder tener todo sincronizado.
Enlaces de interés:
- https://linktr.ee/sebastiandres: Mi directorio de enlaces, el sitio que quería imitar. linktree-ixkge.ondigitalocean.app: Sitio web estático, sirve como demo de “cómo se vería” y para almacenar/enlazar el css y copyright.
- https://github.com/sebastiandres/linktree: Repositorio con las distintas versiones del código. Puedes tomar la última versión de html, css y copyright y personalizarlo a tu antojo.
- ¿Cosas por mejorar? ¡Muchas! ¡Sugerencias y comentarios bienvenidos!
Microcuentos 2021
Cuentos enviados a Santiago en 100 palabras
Canciones pendientes
Es una situación extraña, no me pasa con la pintura, escritura, cine u otras artes. Sin embargo, cuando pienso en la cantidad de canciones que no llegaron a escribirse, me da una pena paralizadora. Estoy completamente seguro que John Lennon nos habría regalado un nuevo Yesterday que nunca llegaremos a escuchar. O que las manos de Victor Jara habrían rasgado de su guitarra otro canto poético y atemporal. Me persigue la imagen de sus muertes sin sentido. ¿Se puede tener nostalgia de una canción que nunca has escuchado?
Cada santo con su milagro
Mi abuela peregrinaba a Lo Vásquez. Mi madre le rezaba a San Expedito. Yo soy apóstol de San Bielsa y le prendo velitas al Omeprazol.
Rudy de Loncoche
Cuando cuando viajábamos con mi abuelo, cada lugar tenía una historia: acá vivía Fulano, acá trabajaba Mengano, ahí se mató Zutano. Muchas veces pensé en sentarme con él, escuchar y anotar cada una de sus historias. Pero me daba susto. Pensaba que esas anécdotas eran como el pelo de Sansón o el talón de Aquiles. Al escribirlas, él perdería esa magia inexplicable que lo había protegido de accidentes y operaciones. Tantos viajes al médico que ya había perdido la cuenta. Al final, la muerte se lo llevó igual y sólo me quedaron algunas de sus más famosas historias.
Gol o penal
Dos mochilas marcan el arco de un equipo. Los sin polera delimitan el suyo con 2 montones de ropa. Nadie mira la hora. La calle tiene sus reglas. El partido sólo termina cuando el perdedor concede la derrota.
Gustavo Adolfo se revuelve en su tumba
¿Qué es poesía?, dices mientras clavas en mi pupila tu pupila azul. Y tras consultar la wikipedia, te respondo. Poesía es un género literario considerado como una manifestación de la belleza o del sentimiento estético por medio de la palabra, en verso o en prosa. Dices que mi modernidad mata el romanticismo. No te compartiré mi excel con la tasa de divorcios, ni mi listado de pros y contras del matrimonio. La decisión fue fácil de evaluar pero difícil de tomar. Una lágrima que no puedo contener, de 0.25 ml, corre lenta por mi mejilla mientras te alejas para siempre.
Santiago en 150 palabras
Sentado-al-computador,-el-escritor-descubrió-que-podía-escribir-cuentos-para-Santiago-en-más-de-cien-palabras. Simplemente/tenía/que/reemplazar/los/espacios/por/otro/símbolo,/y/eso/confundía/al/sistema/de/postulación. No&gritó&eureka&ni&salió&corriendo&desnudo&de&la&bañera. Pensó_que_gracias_al_descubrimiento_todo_sería_más_fácil_al_no_tener_límites_para_su_escritura. Sin^embargo,^y^tras^varios^días^sentado^frente^al^computador,^la^página^seguía^en^blanco^y^su^historia^seguía^sin^escribirse. Llevabatantotiemposoloyencerradoquecarecíadelingredienteprincipal:recuerdosconloscualestejeruna*historia. Todo+lo+recordaba+lejano+y+difuso,+como+una+foto+antigua+cubierta+con+una+fina+capa+de+polvo+gris. Ya·no·recordaba·la·risa·de·los·niños·en·el·parque,·las·bromas·de·una·noche·de·tragos·con·los·amigos,·la·pasión·de·un·romance,·o·un·simple·paseo·sin·rumbo·bajo·los·árboles. Y sin historia que contar, su descubrimiento nunca llegó a ser conocido.
Examen
Sin poderlo evitar, miro de nuevo por la ventana de la biblioteca. Afuera llueve. El mar azota la costa. Adentro, apilo libros de métodos estocásticos sobre mi escritorio. Y aunque todo invita a la emoción y trato de descansar, no puedo evitar pensar ¿caerán las gotas probabilísticamente determinadas?
IPython Display - componentes web
Componentes web para agregar interactividad a Jupyter Notebook/Lab.
Contenidos
1. Antes de: principales errores
1.1 Usando display generosamente
Mi principal aprendizaje es que IPython.display genera objetos de distintos tipos. Los objetos no se muestran o ejecutan por defecto. Si el objeto es el resultado de la celda, el objeto si se despliega o ejecuta.
Si no es el resultado de la celda, no se muestra/ejecuta, a menos que se use el método display.
Diferencias entre hermanos
Jupyter Notebook, Jupyter Lab y Google Colab no son completamente similares. Hasta ahora, lo que he visto:
- En Google Colab está IPython 5.0.0. No es lo mejor para evaluar javascript. De hecho, hay funciones que no existen:
alertypromptno están.
Link interesante: https://colab.research.google.com/notebooks/snippets/advanced_outputs.ipynb#scrollTo=iXZ0xoQd2kCe
2.1 IFrame
IFrame(src,
width,
height,
**kwargs)
Permite insertar una página web como un IFrame (inline frame). La página debe permitirlo (no todas lo hacen). Por ejemplo, para insertar una encuesta o una referencia en un jupyter notebook o una presentación con RISE.
Métodos o atributos del objeto creado:
-
iframe: String con la representación HTML del objeto. No es muy útil.
from IPython.display import IFrame
IFrame("https://es.wikipedia.org/wiki/IFrame", width=800, height=400)
Para más información, ver la documentación con IFrame.?
2.2 Javascript
Javascript(data=None,
url=None,
filename=None,
lib=None,
css=None)
Carga el código javascript en la página. En el notebook, el elemento conteneder será element,
y jQuery estará disponible. Contenido agregado a element serán visibles en la área de output.
Métodos o atributos del objeto creado:
- metadata: Atributo string con la metadata con la que fue creado el objeto.
- reaload: Método para recargar el contenido del objeto, si fue creado desde url o archivo.
from IPython.display import Javascript
Javascript('alert("¡¡Tenemos javascript!!");')
from IPython.display import Javascript
Javascript('console.log("¡¡Tenemos javascript!!");')
from IPython.display import Javascript
js_str = """
var a = 1;
console.log("JS1: a = "+a);
var b = 2;
console.log("JS1: b = " + b);
console.log("JS1: a+b = " + (a+b));
"""
Javascript(js_str)
from IPython.display import Javascript, display
js_str_1 = """
var a = 1;
console.log("JS2: a ="+a);
"""
js_str_2 = """
var b = 2;
console.log("JS2: b =" + b);
console.log("JS2: a+b=" + (a+b));
"""
js_1 = Javascript(js_str_1)
js_2 = Javascript(js_str_2)
display(js_1, js_2)
from IPython.display import Javascript
Javascript?
from IPython.display import Javascript
print("Methods:")
print(sorted([f for f in dir(Javascript) if not f.startswith('_')]))
2.3 HTML
HTML(data=None,
url=None,
filename=None,
metadata=None)
Observación/Consejos:
- No incluir secciones de
<html>,<head>o<body>. - El Javascript vive en la celda de notebook, las variables no se traspasan de una celda a otra. Ver los ejemplos al final.
- En Google Colab algunas funciones de javascript no están disponibles, en particular 'alert'.
Métodos o atributos del objeto creado:
-
metadata: String con la metadata con la que fue creado el objeto. -
reaload(): Método para recargar el contenido del objeto, si fue creado desde url o archivo.
from IPython.display import HTML
HTML("<div style='background-color:lightblue'>Este es un ejemplo.</div>")
# Ver resultado con la consola de javascript
from IPython.display import HTML
my_html = """
<script>
function button_action(value){
var msg = 'La variable tiene valor '+ value;
console.log(msg);
alert(msg);
};
</script>
<div>
Variable:
<input type='text' id='my_input_form' value='42'>
<button onclick='button_action(document.getElementById("my_input_form").value)'>Click</button>
</div>
"""
HTML(data=my_html)
from IPython.display import HTML
my_filename = "html/ejemplo.html"
HTML(filename=my_filename)
from IPython.display import HTML
my_url = "https://raw.githubusercontent.com/sebastiandres/2021-05-IPython-display/master/html/ejemplo.html"
HTML(url=my_url)
from IPython.display import HTML
my_html = """
<script>
var a = 1;
console.log("HTML: a ="+a);
</script>
"""
HTML(my_html)
Más información
from IPython.display import HTML
HTML?
from IPython.display import HTML
print("Methods:")
print(sorted([f for f in dir(HTML) if not f.startswith('_')]))
from IPython.display import HTML
my_html = """
<script>
var num = 42;
console.log("HTML1A: num = " + num);
</script>
"""
HTML(my_html)
from IPython.display import Javascript
my_html = """
<script>
var b = 2;
console.log("HTML1B1: num =" + num);
console.log("HTML1B2: b =" + b);
console.log("HTML1B3: num+b=" + (num+b));
</script>
"""
HTML(my_html)
from IPython.display import HTML
my_html = """
<script>
var a = 1;
console.log("HTML2A: a = "+a);
</script>
<script>
var b = 2;
console.log("HTML2B: b = "+b);
</script>
<script>
console.log("HTML2C: a+b = "+(a+b));
</script>
"""
HTML(my_html)
from IPython.display import HTML, display
my_html = """
<script>
var a = 1;
console.log("HTM3: a = "+a);
</script>
"""
h1 = HTML(my_html)
my_html = """
<script>
var b = 2;
console.log("HTML3: b = "+b);
</script>
"""
h2 = HTML(my_html)
my_html = """
<script>
console.log("HTML3: a+b = "+(a+b));
</script>
"""
h3 = HTML(my_html)
display(h1, h2, h3) # Hay que poner todos, sino solo muestra el último
!cat body.html
from IPython.display import HTML, Javascript, display
HTML(filename="body.html")
from IPython.display import HTML, Javascript, display
my_path = "https://raw.githubusercontent.com/sebastiandres/2021-05-IPython-display/master/"
my_js = Javascript(url=my_path+"body.js", lib=my_path+"reveal.js", css=my_path+"reveal.css")
my_html = HTML(filename="body.html")
display(my_js)
display(my_html)
from IPython.display import HTML, Javascript, display
my_js = """
var link = document.createElement('link');
link.rel = "stylesheet"; link.type = "text/css";
link.href = "reveal.css";
document.querySelector('head').appendChild(link);
"""
Javascript(my_js)
from IPython.display import Javascript
js_str_1 = """
var num = 42;
console.log("JS1A: num = " + num);
"""
Javascript(js_str_1)
from IPython.display import Javascript
js_str_2 = """
var num = 42;
var b = 2;
console.log("JS1B1: num = " + num);
console.log("JS1B2: b = " + b);
console.log("JS1B3: num+b = " + (num+b));
"""
Javascript(js_str_2)
from IPython.display import Javascript
my_js = """
function button_action(value){
console.log('La variable tiene valor '+ value);
};
element.append(`<div>
Variable:
<input type='text' id='my_input_form' value='42'>
<button onclick='button_action(document.getElementById("my_input_form").value)'>Click</button>
</div>`);
"""
Javascript(my_js)
# https://github.com/timqian/chart.xkcd
from IPython.display import Javascript
my_js = """
const svg = document.querySelector('.line-chart')
new chartXkcd.Line(svg, {
title: 'Monthly income of an indie developer',
xLabel: 'Month',
yLabel: '$ Dollars',
data: {
labels:['1', '2', '3', '4', '5', '6','7', '8', '9', '10'],
datasets: [{
label: 'Plan',
data: [30, 70, 200, 300, 500 ,800, 1500, 2900, 5000, 8000],
}, {
label: 'Reality',
data: [0, 1, 30, 70, 80, 100, 50, 80, 40, 150],
}]
},
options: {}
});
element.append(`<svg class="line-chart"></svg>`)
"""
my_lib = "https://cdn.jsdelivr.net/npm/chart.xkcd@1/dist/chart.xkcd.min.js"
Javascript(data=my_js, lib=my_lib)
from IPython.display import Javascript
# Link: https://github.com/timqian/chart.xkcd/blob/master/examples/index.js
my_url = "https://raw.githubusercontent.com/timqian/chart.xkcd/master/examples/index.js"
my_lib = "https://cdn.jsdelivr.net/npm/chart.xkcd@1/dist/chart.xkcd.min.js"
Javascript(url=my_url, lib=my_lib)
import IPython
js_code = \
'''
let message = "Hello world!";
//document.querySelector("#output-area").appendChild(document.createTextNode(message));
element.append(document.createTextNode(message));
'''
display(IPython.display.Javascript(js_code))
import IPython
js_code = \
'''
var a = prompt("Hello", "Sebas");
alert(a);
'''
display(IPython.display.Javascript(js_code))
from IPython.display import HTML, Image
canvas_html = """
<canvas width=%d height=%d style="background-color:rgb(240,240,240)"></canvas>
<button>Guess Number</button>
<script>
var canvas = document.querySelector('canvas')
var ctx = canvas.getContext('2d')
ctx.lineWidth = %d
var button = document.querySelector('button')
var mouse = {x: 0, y: 0}
canvas.addEventListener('mousemove', function(e) {
mouse.x = e.pageX - this.offsetLeft
mouse.y = e.pageY - this.offsetTop
})
canvas.onmousedown = ()=>{
ctx.beginPath()
ctx.moveTo(mouse.x, mouse.y)
canvas.addEventListener('mousemove', onPaint)
}
canvas.onmouseup = ()=>{
canvas.removeEventListener('mousemove', onPaint)
}
var onPaint = ()=>{
ctx.lineTo(mouse.x, mouse.y)
ctx.stroke()
}
var data = new Promise(resolve=>{
button.onclick = ()=>{
resolve(canvas.toDataURL('image/png'))
}
})
</script>
"""
print(canvas_html % (280, 280, 10))
def draw(filename='drawing.png', w=280, h=280, line_width=10):
display(HTML(canvas_html % (w, h, line_width)))
draw()
from IPython.display import HTML
HTML(filename="drawing.html")
IPython Display - componentes no web
Componentes no web para agregar interactividad a Jupyter Notebook/Lab.
Contenidos
- 1. Antes de: principales errores
- 2. Diferencias Jupyter Notebook/Lab y Google Colab
- 3 Imágenes
- 4 Video
- 5 Audio
- 6 Código o Texto
- Enfasis
- Otros
- 7 Otros
- 8. Sin representación gráfica directa
- 3. Enlaces
Probaremos los siguientes:
-
Imágenes
- SVG
- Image
-
Video
- Video
- VimeoVideo
- YoutubeVideo
-
Audio
- Audio
-
Texto o Código
- Code
- FileLink y FileLinks
- Javascript
- Latex
- Markdown
- Math
- Scribd Document
-
Otros
- JSON
- GEOJSon
- TextDisplayObject
- DisplayObject
- DisplayHandle
- Pretty
1. Antes de: principales errores
Mi principal aprendizaje es que lo que se genera son objetos de distintos tipos. Los objetos no se muestran por defecto. Si el objeto es el resultado de la celda, el objeto se despliega por defecto. Sino es el resultado de la celda, no se muestra.
Es posible mostrar uno o varios objetos, con la función display. Display funciona en jupyter notebook/lab, pero no en python o IPython.
from IPython.display import Audio
Audio("http://www.w3schools.com/html/horse.ogg")
from IPython.display import Audio
a = Audio("http://www.w3schools.com/html/horse.ogg")
from IPython.display import Audio, display
a = Audio("http://www.w3schools.com/html/horse.ogg")
display(a)
from IPython.display import Audio, Image, display
a = Audio("http://www.w3schools.com/html/horse.ogg")
i1 = Image(filename="2021-05-ipython-display/Python.png", width=100)
i2 = Image(filename="2021-05-ipython-display/Python.jpg", width=150)
display(a,i1,i2)
3.1 Image
Image(data=None,
url=None,
filename=None,
format=None,
embed=None,
width=None,
height=None,
retina=False,
unconfined=False,
metadata=None)
Métodos o atributos del objeto creado:
- metadata: Atributo string con la metadata con la que fue creado el objeto.
- reaload: Método para recargar el contenido del objeto, si fue creado desde url o archivo.
from IPython.display import Image
Image(filename="2021-05-ipython-display/Python.png", width=100)
from IPython.display import Image
Image(filename="2021-05-ipython-display/Python.jpg", width=150)
from IPython.display import Image
Image(filename="2021-05-ipython-display/Python.gif", width=100)
from IPython.display import Image
my_url = "https://raw.githubusercontent.com/sebastiandres/2021-05-IPython-display/master/images/Python.png"
Image(url=my_url, width=100)
from IPython.display import Image
import numpy as np
my_data = ""
a = Image(data=my_data)
Más información
from IPython.display import Image
Image?
2.18 SVG
class IPython.display.SVG(data=None,
url=None,
filename=None,
metadata=None)
Un objeto SVG creado tiene los siguientes métodos o atributos:
- data: Entrega los datos del SVG.
- metadata: String con la metadata proporcionada en la creación.
- reload: Método para recargar el SVG, si fue creado por url o filename.
from IPython.display import SVG
SVG(filename="2021-05-ipython-display/Python.svg")
from IPython.display import SVG
SVG(url="https://raw.githubusercontent.com/sebastiandres/2021-05-IPython-display/master/images/Python.svg")
from IPython.display import SVG, display
data = '<svg height="109.8461" id="svg2169" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.45.1" sodipodi:docbase="/home/bene/Desktop" sodipodi:docname="dessin-1.svg" sodipodi:version="0.32" version="1.0" width="110.4211" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n <defs id="defs2171">\n <linearGradient id="linearGradient11301" inkscape:collect="always">\n <stop id="stop11303" offset="0" style="stop-color:#ffe052;stop-opacity:1"/>\n <stop id="stop11305" offset="1" style="stop-color:#ffc331;stop-opacity:1"/>\n </linearGradient>\n <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient11307" inkscape:collect="always" x1="89.136749" x2="147.77737" xlink:href="#linearGradient11301" y1="111.92053" y2="168.1012"/>\n <linearGradient id="linearGradient9515" inkscape:collect="always">\n <stop id="stop9517" offset="0" style="stop-color:#387eb8;stop-opacity:1"/>\n <stop id="stop9519" offset="1" style="stop-color:#366994;stop-opacity:1"/>\n </linearGradient>\n <linearGradient gradientUnits="userSpaceOnUse" id="linearGradient9521" inkscape:collect="always" x1="55.549179" x2="110.14919" xlink:href="#linearGradient9515" y1="77.070274" y2="131.85291"/>\n </defs>\n <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" height="184.25197px" id="base" inkscape:current-layer="layer1" inkscape:cx="-260.46312" inkscape:cy="316.02744" inkscape:document-units="px" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="624" inkscape:window-width="872" inkscape:window-x="5" inkscape:window-y="48" inkscape:zoom="0.24748737" pagecolor="#ffffff" width="131.10236px"/>\n <metadata id="metadata2174">\n <rdf:RDF>\n <cc:Work rdf:about="">\n <dc:format>image/svg+xml</dc:format>\n <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <g id="layer1" inkscape:groupmode="layer" inkscape:label="Calque 1" transform="translate(-473.36088,-251.72485)">\n <g id="g1894" transform="translate(428.42338,184.2561)">\n <path d="M 99.75,67.46875 C 71.718268,67.468752 73.46875,79.625 73.46875,79.625 L 73.5,92.21875 L 100.25,92.21875 L 100.25,96 L 62.875,96 C 62.875,96 44.9375,93.965724 44.9375,122.25 C 44.937498,150.53427 60.59375,149.53125 60.59375,149.53125 L 69.9375,149.53125 L 69.9375,136.40625 C 69.9375,136.40625 69.433848,120.75 85.34375,120.75 C 101.25365,120.75 111.875,120.75 111.875,120.75 C 111.875,120.75 126.78125,120.99096 126.78125,106.34375 C 126.78125,91.696544 126.78125,82.125 126.78125,82.125 C 126.78125,82.124998 129.04443,67.46875 99.75,67.46875 z M 85,75.9375 C 87.661429,75.937498 89.8125,78.088571 89.8125,80.75 C 89.812502,83.411429 87.661429,85.5625 85,85.5625 C 82.338571,85.562502 80.1875,83.411429 80.1875,80.75 C 80.187498,78.088571 82.338571,75.9375 85,75.9375 z " id="path8615" style="opacity:1;color:#000000;fill:url(#linearGradient9521);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"/>\n <path d="M 100.5461,177.31485 C 128.57784,177.31485 126.82735,165.1586 126.82735,165.1586 L 126.7961,152.56485 L 100.0461,152.56485 L 100.0461,148.7836 L 137.4211,148.7836 C 137.4211,148.7836 155.3586,150.81787 155.3586,122.53359 C 155.35861,94.249323 139.70235,95.252343 139.70235,95.252343 L 130.3586,95.252343 L 130.3586,108.37734 C 130.3586,108.37734 130.86226,124.03359 114.95235,124.03359 C 99.042448,124.03359 88.421098,124.03359 88.421098,124.03359 C 88.421098,124.03359 73.514848,123.79263 73.514848,138.43985 C 73.514848,153.08705 73.514848,162.6586 73.514848,162.6586 C 73.514848,162.6586 71.251668,177.31485 100.5461,177.31485 z M 115.2961,168.8461 C 112.63467,168.8461 110.4836,166.69503 110.4836,164.0336 C 110.4836,161.37217 112.63467,159.2211 115.2961,159.2211 C 117.95753,159.2211 120.1086,161.37217 120.1086,164.0336 C 120.10861,166.69503 117.95753,168.8461 115.2961,168.8461 z " id="path8620" style="opacity:1;color:#000000;fill:url(#linearGradient11307);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"/>\n </g>\n </g>\n</svg>'
my_svg = SVG(data=data, metadata = "SVG creado por datos")
display(my_svg)
print(my_svg.metadata)
Más información:
from IPython.display import SVG
SVG?
from IPython.display import Video
Video("2021-05-ipython-display/Python.webm",
width=600,
height=400)
from IPython.display import Video
Video("https://upload.wikimedia.org/wikipedia/commons/transcoded/b/b4/Ball_python_%28Python_regius%29_in_a_zoo.webm/Ball_python_%28Python_regius%29_in_a_zoo.webm.1080p.vp9.webm",
width=600,
height=400)
from IPython.display import Video
Video('2021-05-ipython-display/Python.webm', embed=True, html_attributes="no-controls muted autoplay", width=600, height=400)




